﻿using System;
using System.Threading;

namespace PhenomMsrTweaker
{
	/// <summary>
	/// CPU settings for all cores and a given P-state.
	/// </summary>
	public class PState
	{
		private static readonly int _numCores = System.Environment.ProcessorCount;

		private PStateMsr[] _msrs = new PStateMsr[_numCores];


		/// <summary>
		/// Gets the per-core settings.
		/// </summary>
		public PStateMsr[] Msrs { get { return _msrs; } }


		/// <summary>
		/// Private constructor. Use Load() or Decode() to create an instance.
		/// </summary>
		private PState()
		{
		}


		/// <summary>
		/// Loads the specified P-state from the cores' MSRs.
		/// </summary>
		/// <param name="index">Index of the hardware P-state (0-4) to be loaded.</param>
		public static PState Load(int index)
		{
			if (index < 0 || index > 4)
				throw new ArgumentOutOfRangeException("index");

			var r = new PState();

			for (int i = 0; i < _numCores; i++)
				r._msrs[i] = PStateMsr.Load(index, i);

			return r;
		}

		/// <summary>
		/// Saves the P-state to the cores' MSRs.
		/// </summary>
		/// <param name="index">Index of the hardware P-state (0-4) to be modified.</param>
		public void Save(int index)
		{
			if (index < 0 || index > 4)
				throw new ArgumentOutOfRangeException("index");

			uint msrIndex = 0xC0010064u + (uint)index;

			int boostedStates = K10Manager.GetNumBoostedStates();
			int indexSw = Math.Max(0, index - boostedStates);

			int tempPStateHw = (index <= boostedStates ? K10Manager.GetHighestPState() : 0);
			int tempPStateSw = Math.Max(0, tempPStateHw - boostedStates);

			// switch temporarily to the highest thread priority
			// (we try not to interfere with any kind of C&Q)
			var previousPriority = Thread.CurrentThread.Priority;
			Thread.CurrentThread.Priority = ThreadPriority.Highest;

			bool[] applyImmediately = new bool[_numCores];
			for (int i = 0; i < _numCores; i++)
			{
				applyImmediately[i] = (K10Manager.GetCurrentPState(i) == index);

				// if the settings are to be applied immediately, switch temporarily to another P-state
				if (applyImmediately[i])
					K10Manager.SwitchToPState(tempPStateSw, i);
			}
			Thread.Sleep(3); // let transitions complete
			for (int i = 0; i < _numCores; i++)
			{
				// save the new settings
				ulong msr = Program.Ols.ReadMsr(msrIndex, i);
				const ulong mask = 0xFE00FFFFu;
				msr = (msr & ~mask) | (_msrs[i].Encode() & mask);
				Program.Ols.WriteMsr(msrIndex, msr, i);

				// apply the new settings by switching back
				if (applyImmediately[i])
					K10Manager.SwitchToPState(indexSw, i);
			}
			Thread.Sleep(3); // let transitions complete

			Thread.CurrentThread.Priority = previousPriority;
		}


		/// <summary>
		/// Decodes a P-state from its string representation.
		/// </summary>
		/// <returns></returns>
		public static PState Decode(string text)
		{
			if (string.IsNullOrEmpty(text))
				return null;

			string[] tokens = text.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
			if (tokens == null || tokens.Length != _numCores)
				return null;

			var r = new PState();

			for (int i = 0; i < _numCores; i++)
			{
				uint value = uint.Parse(tokens[i], System.Globalization.NumberStyles.HexNumber);
				r._msrs[i] = PStateMsr.Decode(value);
			}

			return r;
		}

		/// <summary>
		/// Encodes the P-state into a string.
		/// </summary>
		public string Encode()
		{
			var sb = new System.Text.StringBuilder();

			for (int i = 0; i < _numCores; i++)
			{
				uint value = _msrs[i].Encode();

				sb.Append(value.ToString("X8"));

				if (i < _numCores - 1)
					sb.Append('|');
			}

			return sb.ToString();
		}


		/// <summary>
		/// Returns a human-readable string representation.
		/// </summary>
		public override string ToString()
		{
			var sb = new System.Text.StringBuilder();

			double maxCpuVid = 0, maxNbVid = 0;
			for (int i = 0; i < _msrs.Length; i++)
			{
				sb.Append(_msrs[i].Multi);
				if (i < _numCores - 1)
					sb.Append('|');

				maxCpuVid = Math.Max(maxCpuVid, _msrs[i].CpuVid);
				maxNbVid = Math.Max(maxNbVid, _msrs[i].NbVid);
			}

			sb.AppendFormat(" @ {0}V/{1}V", maxCpuVid, maxNbVid);

			return sb.ToString();
		}
	}
}
